package org.rency.ues.client.impl;

import java.io.UnsupportedEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import org.rency.common.utils.enums.CharsetEnum;
import org.rency.common.utils.proxy.IntegrationProxy;
import org.rency.common.utils.security.DESUtils;
import org.rency.common.utils.security.X509CertUtils;
import org.rency.ues.client.UesClient;
import org.rency.ues.client.support.UesContext;
import org.rency.ues.common.UesDict;
import org.rency.ues.common.enums.EncryptType;
import org.rency.ues.facade.UesFacade;
import org.rency.ues.request.EncryptRequest;
import org.rency.ues.request.UesRequest;
import org.rency.ues.response.UesResponse;
import org.springframework.util.Assert;

public class UesClientImpl implements UesClient {

	private IntegrationProxy<UesFacade> uesFacade;
	
	@Override
	public boolean saveData(UesContext context) {
		Assert.notNull(context, "ues context is empty.");
		Assert.notNull(context.getClientId(), "client id is empty");
		Assert.notNull(context.getPlainData(), "encrypt data is empty");
		String plainData = context.getPlainData();
		try{
			X509Certificate cert = this.getCertification(context.getClientId(),context.getVersion());
			this.setEncryptData(context, cert);
			UesResponse response = uesFacade.buildInst().saveData(this.build(context));
			context.setPlainData(plainData);
			if(!response.isSuccess()){
				context.setSuccess(false);
				context.setReturnMessage(response.getReturnMessage());
				return response.isSuccess();
			}
			context.setSuccess(true);
			context.setTicket(response.getTicket());
		}catch (Exception e) {
			context.setSuccess(false);
			context.setReturnMessage(e.getMessage());
			return false;
		}
		return true;
	}

	@Override
	public boolean getByTicket(UesContext context) {
		Assert.notNull(context, "ues context is empty.");
		Assert.notNull(context.getClientId(), "client id is empty");
		Assert.notNull(context.getTicket(), "encrypt data ticket is empty");
		try{
			UesResponse response = uesFacade.buildInst().getByTicket(new UesRequest(context.getTicket(),context.getEncryptType(),context.getClientId()));
			if(!response.isSuccess()){
				context.setSuccess(false);
				context.setReturnMessage(response.getReturnMessage());
				return response.isSuccess();
			}
			context.setSuccess(true);
			context.setPlainData(response.getData());
		}catch (Exception e) {
			context.setSuccess(false);
			context.setReturnMessage(e.getMessage());
			return false;
		}
		return true;
	}

	private X509Certificate getCertification(String clientId,long version) throws CertificateException{
		EncryptRequest request = new EncryptRequest();
		request.setSerialNo(clientId);
		request.setCertVersion(version);
		UesResponse response = uesFacade.buildInst().getCertBase(request);
		Assert.notNull(response, "client id is invalid.");
		Assert.isTrue(response.isSuccess(), "get remote cert error.");
		Assert.hasText(response.getData(), "get remote cert base error.");
		X509Certificate cert = X509CertUtils.getX509CertFromBase64Cert(response.getData());
		Assert.notNull(cert, "can't load x509 certification.");
		return cert;
	}
	
	private void setEncryptData(UesContext context,X509Certificate cert){
		String randomKey = X509CertUtils.generateRandomKey(UesDict.DEFAULT_RANDOM_KEY_LENGTH);
		String encryptData = null;
		try {
			encryptData = DESUtils.encryptDESede(context.getPlainData(), randomKey);
		} catch (Exception e) {
			throw new RuntimeException("encrypt plain data error, please contact adminitrator.",e);
		}
		byte[] randomKeyBytes;
		try {
			randomKeyBytes = randomKey.getBytes(CharsetEnum.UTF_8.getCode());
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("encrypt plain data error, please contact adminitrator.",e);
		}
		String encryptedKey = this.getEncryptKey(context.getEncryptType(),randomKeyBytes,cert);
		context.setPlainData(encryptData);
		context.setTicket(encryptedKey);
	}
	
	private String getEncryptKey(EncryptType encryptType, byte[] data, X509Certificate cert){
		try{
			switch(encryptType){
				case RSA:
					return X509CertUtils.encryptRSA(data, cert);
				case 	CMS:
					return X509CertUtils.encryptCMS(data, cert);
				default :
					throw new RuntimeException("Encrypt Type ["+encryptType+"] not support!");	
			}
		}catch(RuntimeException e){
			throw e;
		}catch (Exception e) {
			throw new RuntimeException("encrypt data error with EncryptType["+encryptType+"]");
		}
	}

	private EncryptRequest build(UesContext context){
		EncryptRequest to = new EncryptRequest();
		to.setEncryptData(context.getPlainData());
		to.setEncryptKey(context.getTicket());
		to.setEncryptType(context.getEncryptType());
		to.setExpireTime(context.getExpireTime());
		to.setSerialNo(context.getClientId());
		to.setCertVersion(context.getVersion());
		return to;
	}
	

	public void setUesFacade(IntegrationProxy<UesFacade> uesFacade) {
		this.uesFacade = uesFacade;
	}
}
